home *** CD-ROM | disk | FTP | other *** search
- /*
- * tacpasswd
- *
- * This program maintains the /etc/tacpasswd file. It can be used to
- * generate or change passwords for the TACACS system.
- *
- * The password generator routines are from Brandon Allbery, as
- * distribured in comp.sources.misc, volume 5, pwgen.
- *
- * Written by: David Hampton, 30 June 89
- */
-
- #ifdef SYSV
- #define random rand
- #define srandom srand
- #endif
-
- /*
- Path: xanth!mcnc!gatech!cwjcc!hal!ncoast!allbery
- From: allbery@ncoast.UUCP (Brandon S. Allbery)
- Newsgroups: comp.sources.misc
- Subject: v05i059: pwgen -- random-but-pronounceable password generator
- Message-ID: <13184@ncoast.UUCP>
- Date: 26 Nov 88 04:44:18 GMT
- Sender: allbery@ncoast.UUCP
- Reply-To: allbery@ncoast.UUCP (Brandon S. Allbery)
- Organization: Cleveland Public Access UN*X, Cleveland, Oh
- Lines: 328
- Approved: allbery@ncoast.UUCP
-
- Posting-number: Volume 5, Issue 59
- Submitted-by: "Brandon S. Allbery" <allbery@ncoast.UUCP>
- Archive-name: pwgen
-
- [Dr. Jekyll and Mr. Hyde, anyone? ;-) ++bsa]
-
- I've described this program, in its OSI Superboard-II incarnation, in
- comp.unix.wizards and news.sysadmin. Now, here's the code for a UN*X
- version. It seems to work OK on ncoast, except for some oddness in the
- "random" selections -- which are probably the result of our not having a
- good RNG. (Could someone mail me one or more of the alternative
- generators? Or maybe even the source to BSD random(), assuming that it
- falls outside the subset of BSD code that can be traced to AT&T? To put it
- mildly, our random number generator isn't.)
-
- This is not as complete as the original program, which actually had a list
- of characters (the current one uses phonemes) which were most likely to
- follow other phonemes. On the other hand, this version does use phonemes
- rather than characters, so its creations are more pronounceable as a rule
- than those of the original. The resulting passwords aren't quite as
- "natural" as the original, however. (That may be for the best; the original
- was intended as a simple experiment to see if a rule for "proper English
- words" could be defined in terms of rules which related letters. This was
- before I got to college and learned that such things were rather unlikely,
- but the original program still did a pretty good job of spewing out some
- common English words. That can't be said for *this* program.)
-
- To compile: cc -O -o pwgen pwgen.c -lm
- (The sqrt() function is used in an attempt to produce a random number which
- is "weighted" toward one end of the range, so as to prefer one end of a list
- of "spellings" for a phoneme over the other end. It seems to work, but with
- this rotted RNG, I can't be absolutely certain. A trial distribution seems
- to be correct, however.)
-
- What's the intent? I find that I can remember a "word" better if I can
- pronounce it. This may or may not be true for other people, but *anything*
- that encourages the use of random passwords is an improvement on what I
- typically see at client sites every day. So this program spits out
- passwords which are virtually guaranteed not to be found in the dictionary,
- but are (usually) pronounceable to speakers of English. (The tables can be
- modified for other languages, but they're rather hairy. Perhaps I'll write
- a version which loads "compiled" language descriptions, and let the compiler
- deal with the hairy stuff. Hey, they were even hairier in the BASIC
- version!)
-
- Oh, well, shar and enjoy.
-
- ++Brandon, sitting on the other side of the fence for the moment
- */
-
- /*
- * Generate (hopefully) pronounceable random passwords. These can often be
- * remembered more easily than completely random passwords, and are immune to
- * dictionary searches, etc.
- *
- * The original version of this program was written in BASIC on an OSI
- * Superboard II SBC. That version is long gone (the SB2's cassette drive
- * was never trustworthy, it eventually scrambled the tape), but the basic
- * (pardon the pun) idea lives on here, with a few modification like basing
- * the selection on "graphs" (actually, they are a restricted set of phonemes)
- * and having randomly-selected spellings for those graphs.
- */
-
- #include <stdio.h>
- #include <math.h>
-
- #define RANDOM(c) ((int) (((random(c) & 0x7fff) / 32767.0) * (c)))
-
- char *spelling[] = {
- /*a*/ "a", (char *) 0, /* 2*/
- /*A*/ "a", "ae", "ai", (char *) 0, /* 6*/
- /*b*/ "b", (char *) 0, /* 8*/
- /*ch*/ "ch", (char *) 0, /*10*/
- /*d*/ "d", (char *) 0, /*12*/
- /*e*/ "e", (char *) 0, /*14*/
- /*E*/ "e", "ee", "ie", (char *) 0, /*18*/
- /*f*/ "f", "ph", "gh", (char *) 0, /*22*/
- /*g*/ "g", (char *) 0, /*24*/
- /*h*/ "h", (char *) 0, /*26*/
- /*i*/ "i", "e", (char *) 0, /*29*/
- /*I*/ "i", "ai", (char *) 0, /*32*/
- /*i'*/ "i", "ei", (char *) 0, /*35*/
- /*j*/ "j", "g", (char *) 0, /*38*/
- /*k*/ "k", "c", (char *) 0, /*41*/
- /*l*/ "l", (char *) 0, /*43*/
- /*m*/ "m", (char *) 0, /*45*/
- /*n*/ "n", (char *) 0, /*47*/
- /*ng*/ "ng", (char *) 0, /*49*/
- /*o*/ "o", "a", "ah", (char *) 0, /*53*/
- /*O*/ "o", "oh", (char *) 0, /*56*/
- /*oo*/ "oo", "u", (char *) 0, /*59*/
- /*OO*/ "oo", "w", (char *) 0, /*62*/
- /*p*/ "p", (char *) 0, /*64*/
- /*qu*/ "qu", (char *) 0, /*66*/
- /*r*/ "r", (char *) 0, /*68*/
- /*s*/ "s", "c", (char *) 0, /*71*/
- /*sh*/ "sh", "s", (char *) 0, /*74*/
- /*t*/ "t", (char *) 0, /*76*/
- /*th*/ "th", (char *) 0, /*78*/
- /*TH*/ "th", (char *) 0, /*80*/
- /*u*/ "u", (char *) 0, /*82*/
- /*U*/ "u", "oo", (char *) 0, /*85*/
- /*v*/ "v", (char *) 0, /*87*/
- /*x*/ "x", (char *) 0, /*89*/
- /*y*/ "y", (char *) 0, /*91*/
- /*z*/ "z", "s", (char *) 0, /*94*/
- };
-
- struct graph {
- char *graph;
- char type;
- #define CONSONANT 0
- #define VOWEL_LONG 1
- #define VOWEL_SHORT 2
- #define VOWEL_OTHER 3
- #define VOWEL_MASK 3
- #define iscons(c) (((c)->type & VOWEL_MASK) == 0)
- #define isvowel(c) (((c)->type & VOWEL_MASK) != 0)
- /* char frequency; */ /* unused for now */
- char **spellings;
- /* struct graph **following; */ /* maybe later */
- } graph[] = {
- "a", VOWEL_SHORT, &spelling[0],
- "A", VOWEL_LONG, &spelling[2],
- "b", CONSONANT, &spelling[6],
- "ch", CONSONANT, &spelling[8],
- "d", CONSONANT, &spelling[10],
- "e", VOWEL_SHORT, &spelling[12],
- "E", VOWEL_LONG, &spelling[14],
- "f", CONSONANT, &spelling[18],
- "g", CONSONANT, &spelling[22],
- "h", CONSONANT, &spelling[24],
- "i", VOWEL_SHORT, &spelling[26],
- "I", VOWEL_LONG, &spelling[29],
- "i'", VOWEL_OTHER, &spelling[32],
- "j", CONSONANT, &spelling[35],
- "k", CONSONANT, &spelling[38],
- "l", CONSONANT, &spelling[41],
- "m", CONSONANT, &spelling[43],
- "n", CONSONANT, &spelling[45],
- "ng", CONSONANT, &spelling[47],
- "o", VOWEL_SHORT, &spelling[49],
- "O", VOWEL_LONG, &spelling[53],
- "oo", VOWEL_SHORT, &spelling[56],
- "OO", VOWEL_LONG, &spelling[59],
- "p", CONSONANT, &spelling[62],
- "qu", CONSONANT, &spelling[64],
- "r", CONSONANT, &spelling[66],
- "s", CONSONANT, &spelling[68],
- "sh", CONSONANT, &spelling[71],
- "t", CONSONANT, &spelling[74],
- "th", CONSONANT, &spelling[76],
- "TH", CONSONANT, &spelling[78],
- "u", VOWEL_SHORT, &spelling[80],
- "U", VOWEL_LONG, &spelling[82],
- "v", CONSONANT, &spelling[85],
- "x", CONSONANT, &spelling[87],
- "y", CONSONANT, &spelling[89],
- "z", CONSONANT, &spelling[91],
- 0, 0, &spelling[94],
- };
-
- struct graph *vowel[] = {
- &graph[0], &graph[1], &graph[5], &graph[6],
- &graph[10], &graph[11], &graph[12], &graph[19],
- &graph[20], &graph[21], &graph[22], &graph[30],
- &graph[31],
- (struct graph *) 0,
- };
-
- struct graph *consonant[] = {
- &graph[2], &graph[3], &graph[4], &graph[7],
- &graph[8], &graph[9], &graph[13], &graph[14],
- &graph[15], &graph[16], &graph[17], &graph[18],
- &graph[23], &graph[24], &graph[25], &graph[26],
- &graph[27], &graph[28], &graph[29], &graph[32],
- &graph[33], &graph[34], &graph[35],
- (struct graph *) 0,
- };
-
- /*
- * Randomly select a graph from the specifield array. Eventually, this should
- * account for graph frequencies as well.
- */
-
- struct graph *selgraph(graphs)
- struct graph **graphs;
- {
- register int cnt;
-
- for (cnt = 0; graphs[cnt] != (struct graph *) 0; cnt++)
- ;
- return graphs[RANDOM(cnt)];
- }
-
- /*
- * Randomly select a spelling for the specified graph. This is not linear:
- * earlier spellings are preferred over later ones, but the latter do
- * sometimes sneak in.
- */
-
- char *selspell(graph)
- struct graph *graph;
- {
- register int cnt, sel;
-
- for (cnt = 0; graph->spellings[cnt] != (char *) 0; cnt++)
- ;
- if (cnt == 0) {
- fprintf(stderr, "PANIC: selspell(%s) got count(spellings) == 0\n", graph->graph);
- exit(2);
- }
- if (cnt == 1)
- return *graph->spellings;
- /*
- * This may not be the best way to do it... maybe Weemba'd care to lend a
- * hand here? After all, my specialty is programming, NOT math.
- */
- if ((sel = cnt - (int) sqrt((double) RANDOM(cnt * cnt) + 1) - 1) < 0 || sel >= cnt) {
- #ifdef BUGCATCH
- fprintf(stderr, "PANIC: selspell(%s) got nlrand(%d) == %d\n", graph->graph, cnt, sel);
- exit(2);
- #else
- sel = 0;
- #endif
- }
- return graph->spellings[sel];
- }
-
- /*
- * Choose the next source for a graph. The rules are: a consonant MUST be
- * followed by a vowel; a vowel may be followed by a vowel of a different
- * type or by a consonant, but never more than two consecutive vowel graphs.
- */
-
- choosenext(cur, prev)
- {
- if (cur == CONSONANT)
- return VOWEL_MASK;
- else if (prev == -1 || (prev & VOWEL_MASK) != 0)
- return CONSONANT;
- else if (RANDOM(10) == 5)
- return VOWEL_MASK;
- else
- return CONSONANT;
- }
-
- /*
- * We are passed an array of (struct graph *); choose an entry randomly and
- * assemble a string fitting the size constraint. We use the original (OSI)
- * paradigm: alternate consonants and vowels, with the option of two vowels
- * in a row occasionally. The only difference is that they must be different
- * *types* of vowels, a distinction that the OSI version didn't consider.
- */
-
- pwgen(initial, pw, maxlen)
- struct graph **initial;
- char *pw;
- {
- int pwlen, state, prev, tmp;
- struct graph *graph;
- char *spelling;
-
- pwlen = 0;
- state = initial[0]->type;
- prev = -1;
- while (pwlen < maxlen - 1) {
- do {
- graph = selgraph(initial);
- } while (state != CONSONANT && graph->type == prev);
- if ((spelling = selspell(graph)) == (char *) 0) {
- fprintf(stderr, "PANIC: got NULL in selspell(%s)\n", graph->graph);
- exit(2);
- }
- strcpy(pw, spelling);
- while (*pw != '\0')
- pwlen++, pw++;
- tmp = prev;
- prev = graph->type;
- if ((state = choosenext(prev, tmp)) == CONSONANT)
- initial = consonant;
- else
- initial = vowel;
- }
- }
-
- /*
- * ============================== End of pwgen ==============================
- */
-
- #include <pwd.h>
- #include <ctype.h>
- #include <sys/types.h>
- #include <sys/stat.h>
-
- #define TACPWFILE "./pwfile"
- #define TMPPWFILE "/tmp/tacpasswd.XXXXXX"
- #define PW_NAMLEN 8 /* max length user name */
- #define PW_PWDLEN 8 /* max length password */
- #define GEN_PWDLEN 8 /* length of gen passwd */
- #define SPACE_PASSWD 20 /* room allocated for enc passwd */
- #define SPACE_DATE 20 /* room allocated for exp date */
- #define SHORTTIME 30
- #define LONGTIME 180
-
- #define FSEEK_F_BEGIN 0
- #define FSEEK_F_CURR 1
- #define FSEEK_F_END 2
-
- /*
- * Externals
- */
- extern char *optarg;
- extern int optind, opterr;
-
- /*
- * Global storage
- */
- char *whoami;
- char *emptystring = "";
- char *usage = "Type '%s -h' for help\n";
- FILE *pwfile, *tmppwfile;
- int generatepwd;
- int expires_in;
-
- /*
- * Forward declarations
- */
- char *mktemp(), *getpass(), *crypt();
- struct passwd *fgetpwent();
-
- /*
- * my_exit
- *
- * Close and remove the temporary file if necessary, and then exit.
- */
- my_exit(code)
- int code;
- {
- if (tmppwfile)
- fclose(tmppwfile);
- exit(code);
- }
-
- /*
- * create_entry
- *
- * Fill in a password structure with all information about a new TAC user.
- */
- struct passwd *create_entry(name, fullname, lastuid)
- char *name, *fullname;
- int lastuid;
- {
- struct passwd *newentry;
-
- fprintf(stderr, "%s: Creating entry for %0.*s\n", whoami, PW_NAMLEN, name);
-
- newentry = (struct passwd *)malloc(sizeof(struct passwd));
- newentry->pw_name = (char *)malloc(strlen(name)+1);
- strncpy(newentry->pw_name, name, PW_NAMLEN);
-
- newentry->pw_passwd = NULL;
- newentry->pw_uid = ++lastuid;
- newentry->pw_gid = lastuid;
- #ifdef PW_QUOTA
- newentry->pw_quota = 0;
- #endif
- newentry->pw_comment = emptystring;
-
- if (fullname) {
- newentry->pw_gecos = (char *)malloc(strlen(fullname)+1);
- strcpy(newentry->pw_gecos, fullname);
- } else
- newentry->pw_gecos = emptystring;
-
- newentry->pw_dir = emptystring;
- newentry->pw_shell = NULL;
- return(newentry);
- }
-
- /*
- * update_expiration
- *
- * Update the expiration date on this entry. Passwords expire 180 days
- * after they are initially set or changed.
- */
- update_expiration(entry)
- struct passwd *entry;
- {
- long now;
- char timestring[26];
-
- time(&now);
- now += expires_in * 24 * 60 * 60;
- strcpy(timestring, ctime(&now));
-
- if (entry->pw_shell)
- free(entry->pw_shell);
- entry->pw_shell = (char *)malloc(SPACE_DATE);
- sprintf(entry->pw_shell, "%6.6s %4.4s, %d", timestring+4, timestring+20,
- expires_in);
- }
-
- /*
- * make_passwd
- *
- * This routine sets up and calls the password generation routines. It
- * encrypts the result, and stores it in the password field of the
- * structure.
- */
- make_passwd(entry)
- struct passwd *entry;
- {
- char genpasswd[20], salt[2];
-
- pwgen((RANDOM(10) < 4? vowel: consonant), genpasswd, GEN_PWDLEN);
-
- salt[0] = '\0';
- salt[1] = '\0';
- while (!isalpha(salt[0]))
- salt[0] = random() & 0x7f ;
- while (!isalpha(salt[1]))
- salt[1] = random() & 0x7f ;
- if (entry->pw_passwd)
- free(entry->pw_passwd);
- entry->pw_passwd = (char *)malloc(SPACE_PASSWD);
- strcpy(entry->pw_passwd, crypt(genpasswd, salt));
- printf("New password for %s is \"%s\"\n", entry->pw_name, genpasswd);
- }
-
- /*
- * read_passwd
- *
- * This routine reads a new password from the terminal.
- */
- read_passwd(entry)
- struct passwd *entry;
- {
- char *p, password[PW_PWDLEN], salt[2];
-
- strncpy(password, getpass("Password: "), PW_PWDLEN);
- p = getpass("Again: ");
- if (strncmp(password, p, PW_PWDLEN) != 0) {
- fprintf(stderr, "Sorry\n");
- my_exit(1);
- }
-
- do {
- salt[0] = random() & 0x7f ;
- } while (!isalpha(salt[0]));
- do {
- salt[1] = random() & 0x7f ;
- } while (!isalpha(salt[1]));
-
- if (entry->pw_passwd)
- free(entry->pw_passwd);
- entry->pw_passwd = (char *)malloc(SPACE_PASSWD);
- strcpy(entry->pw_passwd, crypt(password, salt));
- }
-
- /*
- * copy_pwfile1
- *
- * Copy all entries in the password file before the one we are interested in.
- */
- struct passwd *
- copy_pwfile1(name, lastuid)
- char *name;
- int *lastuid;
- {
- struct passwd *current;
-
- fseek(pwfile, 0, FSEEK_F_BEGIN);
- while (!feof(pwfile)) {
- current = fgetpwent(pwfile);
- if (current && strncmp(current->pw_name, name, PW_NAMLEN) != 0) {
- *lastuid = current->pw_uid;
- if (putpwent(current, tmppwfile) != 0) {
- fprintf(stderr, "%s: Cannot put current entry for %0.*s\n",
- whoami, PW_NAMLEN, current->pw_name);
- perror(whoami);
- my_exit(1);
- }
- } else {
- return(current);
- }
- }
- return(NULL);
- }
-
- /*
- * copy_pwfile2
- *
- * Add the new/modified entry. Copy all entries in the password file after
- * the one we are interested in.
- */
- copy_pwfile2(new)
- struct passwd *new;
- {
- struct passwd *current;
-
- if (putpwent(new, tmppwfile) != 0) {
- fprintf(stderr, "%s: Cannot put new entry for %0.*s\n",
- whoami, PW_NAMLEN, new->pw_name);
- perror(whoami);
- my_exit(1);
- }
-
- while (!feof(pwfile)) {
- current = fgetpwent(pwfile);
- if (current && putpwent(current, tmppwfile) != 0) {
- fprintf(stderr, "%s: Cannot put current entry for %0.*s\n",
- whoami, PW_NAMLEN, current->pw_name);
- perror(whoami);
- my_exit(1);
- }
- }
- }
-
- /*
- * copy_back
- *
- * Copy the new file back into the original file. Can't use rename,
- * because the files aren't guaranteed to be on the same file system.
- */
- copy_back()
- {
- char buffer[4096];
- int count;
-
- fseek(pwfile, 0, FSEEK_F_BEGIN);
- fseek(tmppwfile, 0, FSEEK_F_BEGIN);
-
- while ((count = fread(buffer, sizeof(char), 4096, tmppwfile)) > 0)
- fwrite(buffer, sizeof(char), count, pwfile);
-
- fclose(pwfile);
- fclose(tmppwfile);
- }
-
- /*
- * init
- *
- * Initialize the program.
- */
- init(argc, argv, username, fullname)
- int argc;
- char **argv, **username, **fullname;
- {
- char *c, *tmppwfilename, template[100];
- int opt;
-
- whoami = argv[0];
- generatepwd = 1;
- expires_in = 180;
-
- while ((opt = getopt(argc, argv, "aghlst")) != EOF)
- switch (opt) {
- case 'a':
- generatepwd = 0;
- break;
- case 'g':
- generatepwd = 1;
- break;
- case 'h':
- printf("\n%s: Add an entry to the TACACS password file. The general\n", whoami);
- printf("usage format for this command is:\n\n");
- printf(" %s: [options] username fullname\n\n", whoami);
- printf("The options available are:\n");
- printf(" -a Ask for a password. \n");
- printf(" -g Generate a password. (default)\n");
- printf(" -h This message. \n");
- printf(" -l Long expiration - 180 days. (default)\n");
- printf(" -s Short expiration - 30 days. \n");
- printf(" -t Temporary - 7 days. \n");
- exit(1);
- case 'l':
- expires_in = 180;
- break;
- case 's':
- expires_in = 30;
- break;
- case 't':
- expires_in = 7;
- break;
- default:
- fprintf(stderr, usage, whoami);
- exit(1);
- }
- if ((argc - optind < 1) || (argc - optind > 2)) {
- fprintf(stderr, usage, whoami);
- exit(1);
- }
-
- *username = argv[optind++];
- if (strlen(username) > 8)
- username[8] = '\0';
- *fullname = argv[optind] ? argv[optind] : NULL;
-
- for (c = *username; *c; c++)
- if (!isalnum(*c)) {
- fprintf(stderr, "%s: bad character (%c) in username\n", whoami, *c);
- exit(1);
- }
- if (*fullname)
- for (c = *fullname; *c; c++)
- if ((!isprint(*c)) || (*c == ':')) {
- fprintf(stderr, "%s: bad character (%c) in fullname\n", whoami, *c);
- exit(1);
- }
-
- /*
- * Open TAC password file, and scratch file
- */
- if ((pwfile = fopen(TACPWFILE, "a+")) == NULL) {
- fprintf(stderr, "%s: cannot open TACACS password file\n", whoami);
- perror(whoami);
- my_exit(1);
- }
- strcpy(template, TMPPWFILE);
- tmppwfilename = mktemp(template);
- if ((tmppwfile = fopen(tmppwfilename, "w+")) == NULL) {
- fprintf(stderr, "%s: cannot open temporary file %s\n", whoami, tmppwfilename);
- perror(whoami);
- my_exit(1);
- }
- if (unlink(tmppwfilename) != 0) {
- fprintf(stderr, "%s: cannot unlink temporary file %s\n", whoami, tmppwfilename);
- perror(whoami);
- my_exit(1);
- }
-
- srandom(time(0) + (getpgrp() << 8) + getpid());
- }
-
- main(argc, argv)
- int argc;
- char **argv;
- {
- struct passwd *entry;
- int lastuid = 0;
- char *username, *fullname;
-
- init(argc, argv, &username, &fullname);
- entry = copy_pwfile1(username, &lastuid);
- if (entry == NULL)
- entry = create_entry(username, fullname, lastuid);
- update_expiration(entry);
- if (generatepwd)
- make_passwd(entry);
- else
- read_passwd(entry);
- copy_pwfile2(entry);
- copy_back();
- my_exit(0);
- }
-